/**
 * Sarue
 * @author Felipe Pupo Rodrigues
 * @constructor BSD
 * @description Framework para controlar javascripts dinamicos usando classe, namespace e addons
 */

/*
 * @class Sarue
 * @description Contem todas classes e métodos necessário na inicialização do Sarue
 */
Sarue = {
	propertys:{
		/*
		 * @member Sarue
		 * @method [public] construct
		 * @description Ainda não implementado, ideia é fazer tipo de um seletor mágico parecido com jquery
		 */
		construct: function(){
			
		}
	},
	abstracts:{
		/*
		 * @member Sarue
		 * @property [static]{object} REFERENCE
		 * @description Guarda o elemento SCRIPT em que foi chamado o Sarue
		 */
		REFERENCE: null,
		/*
		 * @member Sarue
		 * @property [static]{string} PATHFW
		 * @description Path onde estao os arquivos do Sarue
		 */
		PATHFW: '',
		/*
		 * @member Sarue
		 * @property [static]{string} PATHJS
		 * @description Path onde estao todos os javascripts
		 */
		PATHJS:'',
		/*
		 * @member Sarue
		 * @property [static]{array} ADDONS
		 * @description Array dos Addons pré carregados
		 */
		ADDONS:[],
		
		/*
		 * @member Sarue
		 * @method [static] initialize
		 * @description Dispara ações iniciais do Sarue, carrega o controle master
		 */
		initialize: function(){
			this._events();
			this._addons();
			
			this.Class.require('Controller.Master', this._runatController);
		},
		
		/*
		 * @member Sarue
		 * @method [static] _runatController
		 * @description Verifica e carrega o controle atual da página
		 */
		_runatController: function(){
			var url = window.location.href;
			var index = (window.location.pathname == '/' || window.location.pathname == '');
			
			var control = this.Tools.getMetaTag('Controller'), instance;
			
			if (!control) {
				var name = index ? 'index' : this.Tools.getFileName(url).replace(/\-+/gim, '_');
				
				if (!name || name == '') {
					name = this.Tools.getRelativePath(url + '/');
				}
				
				control = name.replace(name.charAt(0), name.charAt(0).toUpperCase());
			}
			
			instance = 'Instance.' + control;
			control = 'Controller.' + control;
			
			this.Class.require(control, function(c){
				Sarue.Class.namespace(instance, null, c);
			}, function(){
				Sarue.Class.namespace(instance, null, Controller.Master);
				this.parentNode.removeChild(this);
			});
		},
		
		/*
		 * @member Sarue
		 * @method [static] _events
		 * @description altera eventos de reload e erro da página
		 */
		_events: function(){
			var parent = this;
			
			this.Tools.addHandler(document, 'onkeydown', function(){
				if (window.event && window.event.keyCode == 116) { // Capture and remap F5
					window.event.keyCode = 505;
				}
				
				if (window.event && window.event.keyCode == 505) { // New action for F5
					parent.Tools.pageRefresh();
					return false;
				}
			});
			
			this.Tools.onErrorHandler(function(message, fileName, lineNumber){
				//ignore errors without detail
				if(!message || !fileName || !lineNumber) return false;
				
				//ignore scripts not found
				if(message.search('loading script') != -1) return false;
				
				return parent.Error({
					name: 'Window OnError',
					message: message,
					fileName: fileName,
					lineNumber: lineNumber
				});
			});
		},
		
		/*
		 * @member Sarue
		 * @method [static] _addons
		 * @description Carrega a lista de Addons do preloader
		 */
		_addons: function(){
			if (this.ADDONS) {
				for (var c = 0; c < this.ADDONS.length; c++) {
					var scope = this.ADDONS[c];
					
					if (scope.search(/\./) == -1) {
						scope += '.' + scope;
					};
					
					this.Class.require('Addons.' + scope, null, null, false, true);
				}
			}
		},
		
		/*
		 * @member Sarue
		 * @class Error
		 * @description Trata os erros da página
		 */
		Error: {
			propertys: {
				/*
				 * @member Sarue.Error
				 * @method [public] construct
				 * @param {string} file
				 * @param {array} handlers
				 * @description Adiciona o handler em um escopo de arquivo
				 */
				construct: function(file, handlers){
					if(handlers)
						for(var x = 0; x < handlers.length; x++)
							this.self.addHandler(handlers[x][0], handlers[x][1], handlers[x][2] || file);
				}
			},
			abstracts: {
				TYPES:{},
				IGNORE: [],
				
				/*
				 * @member Sarue.Error
				 * @method [static] construct
				 * @description Adiciona erros comuns
				 */
				construct: function(){
					this('undefined',[
						['CLASS_NOT_DEFINED', '{class} was not defined in {file}.'],
						['METHOD_IS_CAMELCASE', '{property} was not CamelCase in {file}.'],
						['PROPERTY_IS_UPPERCASE', '{property} was not UpperCase in {file}.'],
						['PARAMS_NOT_DEFINED', '{params} is not defined in {function}.'],
						['FILE_NOT_FOUND', '{file} was not found.']
					]);
				},
				
				/*
				 * @member Sarue.Error
				 * @method [static] addHandler
				 * @param {string} name
				 * @param {string} message
				 * @param {string} file ?
				 * @description Adiciona um novo tipo de handler
				 */
				addHandler: function(name, message, file){
					this.TYPES[name] = {
						name: name,
						message: message,
						fileName: file
					};
				},
				
				/*
				 * @member Sarue.Error
				 * @method [static] handler
				 * @param {string} type
				 * @param {object} params ?
				 * @description Dispara um erro
				 */
				handler: function(type, params){
					if (!type) {
						this.handler('PARAMS_NOT_DEFINED', {
							'params': ['type', 'params'],
							'function': 'Sarue.Error.handler',
							'line': 125
						});
					}
					
					var e = this.TYPES[type];
						e.message = Sarue.Tools.srpl(e.message, params);
						e.lineNumber = params ? params.line : 'undefined';
						
					if (!e.fileName && !e.lineNumber)
						return false;
					
					if(this.IGNORE)
				        for (var i = 0, ignore; ignore = this.IGNORE[i]; i++) {
				            if (!ignore.lineNumber && ignore.fileName && e.fileName.search(ignore.fileName) != -1)
				                return true;
				            if (!ignore.fileName && ignore.lineNumber && e.lineNumber == ignore.lineNumber)
				                return true;
				            if (ignore.fileName && e.fileName.search(ignore.fileName) != -1 && ignore.lineNumber && e.lineNumber == ignore.lineNumber)
				                return true;
				        }
			
			        this.display(e);
			
			        return true;
				},
				
				/*
				 * @member Sarue.Error
				 * @method [static] display
				 * @param {object} error
				 * @description Mostra o erro na página
				 */
				display: function (e){
					var error = 'Error (' + e.name + ')\n\n';
			        	error += 'File: ' + e.fileName + '\n';
			        	error += 'Line: ' + e.lineNumber + '\n';
			        	error += 'Message: ' + e.message + '\n';
			
			        alert(error);
				},
				
				/*
				 * @member Sarue.Error
				 * @method [static] ignore
				 * @param {string} file ?
				 * @param {number} line ?
				 * @description Ignora arquivo e linha de erro
				 */
				ignore: function(file, line){
					return this.IGNORE.push({
			            fileName: file,
			            lineNumber: line
			        });
				}
			}
		},
		
		/*
		 * @membrer Sarue
		 * @class Tools
		 * @description Funcões gerais
		 */
		Tools: {
			abstracts: {
				/*
				 * @member Sarue.Tools
				 * @method [static] getFileName
				 * @param {string} url
				 * @return {string} file
				 * @description Pega o nome do arquivo na url
				 */
				getFileName: function(str){
					str = str.substring( str.lastIndexOf("/")+1 ).split('.'); str.pop();
					return str.join('/');
				},
				/*
				 * @member Sarue.Tools
				 * @method [static] getExtension
				 * @param {string} url
				 * @return {string} extension
				 * @description Pega a extenção do arquivo
				 */
				getExtension: function(str){
					return str.substring(str.lastIndexOf(".")+1);
				},
				/*
				 * @member Sarue.Tools
				 * @method [static] getPath
				 * @param {string} url
				 * @return {string} path
				 * @description Pega apenas o path de uma url
				 */
				getPath: function(str){
					str = str.split('/');
					while(str.length > 0 && str.pop() == ""){};
					return str.join('/') + '/';
				},
				/*
				 * @member Sarue.Tools
				 * @method [static] getVars
				 * @param {string} url
				 * @return {object} variables
				 * @description Pega as variáveis GET
				 */
				getVars: function(str){
					var vars = str.split('?')[1] || '';
						vars = vars.split('&');
					
					var objs = {};
					
					for(var c = 0; c < vars.length; c++){
						var attr = vars[c].split('=');
						objs[attr[0]] = attr[1]; 
					}
					
					return objs;
				},
				/*
				 * @member Sarue.Tools
				 * @method [static] getHash
				 * @param {string} url
				 * @return {string} hash
				 * @description Pega o hash de uma url
				 */
				getHash: function(str){
					str = str.split('#');
					return str[1] ? str[1] : '';
				},
				/*
				 * @member Sarue.Tools
				 * @method [static] getRelativePath
				 * @param {string} url
				 * @return {string} path
				 * @description Pega a ultima path da url
				 */
				getRelativePath: function(str){
					str = str.split('/');
					str.pop();
					return str.pop();
				},
				/*
				 * @member Sarue.Tools
				 * @method [static] getMetaTag
				 * @param {string} name
				 * @return {string} value
				 * @description Pega o valor de uma metatag
				 */
				getMetaTag: function(name){
					var metas = document.getElementsByTagName('meta');
					for(var x = 0; x < metas.length; x++){
						if(metas[x] && metas[x].name == name){
							return metas[x].content;
						}
					}
				},
				/*
				 * @member Sarue.Tools
				 * @method [static] pageRefresh
				 * @description Recarrega a página
				 */
				pageRefresh: function(){
					document.location = document.location.href;
				},
				/*
				 * @member Sarue.Tools
				 * @method [static] addHandler
				 * @param {object} obj
				 * @param {string} evnt
				 * @param {function} handler
				 * @description Adiciona um evento em um objeto
				 */
				addHandler: function(obj, evnt, handler) {
				    if (obj.addEventListener) {
				        obj.addEventListener(evnt.replace(/^on/, ''), handler, false);
				    } else {
				        if (obj[evnt]) {
				            var origHandler = obj[evnt];
				            obj[evnt] = function(evt) {
				                origHandler(evt);
				                handler(evt);
				            }
				        } else {
				            obj[evnt] = function(evt) {
				                handler(evt);
				            }
				        }
				    }
				},
				/*
				 * @member Sarue.Tools
				 * @method [static] onErrorHandler
				 * @param {function} handler
				 * @description Adiciona um evento de erro na página
				 */
				onErrorHandler: function(evnt){
					var oFn;
					if (typeof window.onerror == 'function') {
						oFn = window.onerror;
						window.onerror = function(){
							oFn.apply(this, arguments);
							evnt.apply(this, arguments);
						}
					} else {
						window.onerror = evnt;
					}
				},
				/*
				 * @member Sarue.Tools
				 * @method [static] patchId
				 * @param {string} url
				 * @return {string} id
				 * @description Converte uma url em um id válido
				 */
				patchId: function(str){
					try {
						str = str.match(/\:\/\/[^\/]*\/([^\#\?]*)/);
						str = str[1];
						var clean = /[^A-z0-9]/g;
						return str.replace(clean, "_").toLowerCase();
					}catch(e){
						return '';
					}
				},
				/*
				 * @member Sarue.Tools
				 * @method [static] patchId
				 * @param {string} string
				 * @return {string} clean
				 * @description Limpa espaços em branco do comeco e fim de uma string
				 */
				trim: function(str){
					return str.replace(/^\s+|\s+$/g,"");
				},
				/*
				 * @member Sarue.Tools
				 * @method [static] patchId
				 * @param {string} string
				 * @param {object} replace
				 * @param {regexp} format ?
				 * @return {string} string
				 * @description Altera uma string aplicando novo conteudo
				 */
		        srpl: function (str, data, format) {
		            format = format || /\{([A-z\.]*)\}/gi;
		
		            return str.replace(format, function (o, a) {
		                var ns = a.split('.');
		                var sc = data;
		
		                for (var x = 0; x < ns.length; x++) {
		                    sc = sc[ns[x]];
		                }
		
		                var tp = (typeof sc).toLowerCase();
		
		                return (tp != 'function') ? sc : sc.apply(sc, [data, a]);
		            });
		        },
				/*
				 * @member Sarue.Tools
				 * @method [static] sfmt
				 * @param {string} string
				 * @param {string} replace ...
				 * @description Altera uma string aplicando novo conteudo, sem especificar chave
				 */
				sfmt: function(str){
					for(var x = 1, sfmt = []; x < arguments.length; x++)
						sfmt[x - 1] = arguments[x];
					
					return this.srpl(str, sfmt);
				},
				/*
				 * @member Sarue.Tools
				 * @method [static] sfmt
				 * @param {object} object
				 * @return {object} object
				 * @description Clona um objeto e todo seu conteudo
				 */
				clone: function(obj){
					if (obj == null || typeof obj != "object") return obj;
				    if (obj.constructor != Object && obj.constructor != Array) return obj;
				    if (obj.constructor == Date || obj.constructor == RegExp || obj.constructor == Function ||
				        obj.constructor == String || obj.constructor == Number || obj.constructor == Boolean)
				        return new obj.constructor(obj);
				
				    var to = new obj.constructor(),
						clone = Sarue._bios ? Sarue.abstracts.Tools.abstracts.clone : Sarue.Tools.clone;
						
				    for (var name in obj)
				    {
				        to[name] = clone(obj[name], null);
				    }
				
				    return to;
				}
			}
		},
		/*
		 * @member Sarue
		 * @class Loader
		 * @description Classe estatica de carregadores
		 */
		Loader: {
			abstracts: {
				/*
				 * @member Sarue.Loader
				 * @method [static] js
				 * @param {string} url
				 * @param {function} success ?
				 * @param {function} error ?
				 * @param {boolean} sync ?
				 * @return {boolean} status
				 * @description Carrega um arquivo javascript e adiciona na página
				 */
				js: function(url, callback, error, sync){
					callback = callback || function(){};
					error = error || function(){};
					
					var obj = null;
						obj = document.createElement('script');
						//obj.id = Sarue.Tools.patchId(url);
						obj.name = name;
						obj.type = 'text/javascript';
						obj.async = !sync;
						obj.runat = true;
						
					try
					{
						obj.onload = obj.onreadystatechange = function(){
							if (this.runat && (!this.readyState || this.readyState === 'complete' || (this.readyState === 'loaded') )) { //&& this.nextSibling != null
								this.runat = false;
								callback.apply(obj, []);
								this.onload = this.onreadystatechange = null;
							}
							else 
								if (this.readyState === 'loaded' && this.nextSibling == null) {
									error.apply(obj, []);
								}
						}
						
						obj.onerror = function() {
						    error.apply(obj, []);
						}
						
						obj.src = url;
						document.getElementsByTagName('head')[0].appendChild(obj); // Adds element
						
					} 
					catch (e) {
						alert('erro ao incluir javascript');
						return false;
					}
					
					
					return obj;
					
				},
				/*
				 * @member Sarue.Loader
				 * @method [static] css
				 * @param {string} url
				 * @param {function} success ?
				 * @param {function} error ?
				 * @return {boolean} status
				 * @description Carrega um arquivo css e adiciona na página
				 */
				css: function(url, callback, error){
					callback = callback || function(){};
					error = error || function(){};
					
					var name = Sarue.Tools.patchId(url);
					
					var obj = document.createElement('link');
						obj.rel = 'stylesheet';
						obj.id = name;
						obj.name = name;
						obj.type = 'text/css';
					
					try {
						
						obj.onerror = function()
						{
							error.apply(obj, []);
						};
						
						obj.href = url;
						
						document.getElementsByTagName('head')[0].appendChild(obj);
						
						if (callback) 
							callback.apply(obj, []);
							
						return true;
					} 
					catch (e) {
						alert('erro ao incluir css');
						return false;
					}
				}
			}
		},
		/*
		 * @member Sarue
		 * @class Require
		 * @description Carrega arquivos javascript que contem classes
		 */
		Require: {
			propertys: {
				LIST:[],
				SYNC: true,
				
				/*
				 * @member Sarue.Require
				 * @property [public]{object} options
				 * @description Opções particular
				 */
				options: {
					/*
					 * @member Sarue.Require:options
					 * @property [public]{function} finish
					 * @description Função chamada quando a pilha de scripts terminar
					 */
					finish: function(){},
					/*
					 * @member Sarue.Require:options
					 * @property [public]{function} complete
					 * @description Função chamada quando cada script é carregado
					 */
					complete: function(){}
				},
				
				/*
				 * @member Sarue.Require
				 * @method [public] construct
				 * @param {string} src
				 * @param {function} success ?
				 * @param {function} error ?
				 * @param {boolean} async ?
				 * @description Adiciona um script de classe necessário
				 */
				construct: function(src, callback, error, async){
					var thiz = Sarue._bios ? Sarue.abstracts.Require.propertys : this,
						self = Sarue._bios ? Sarue.abstracts.Require.abstracts : this.self;
					
					self.INSTANCES.push(thiz);
					
					if(!src && !callback && !error && !asyn)
						return thiz;
					
					if(!async){
						self.ALLSYNC = self.ALLSYNC || thiz;
						self.ALLSYNC.add(src, callback, error);
						
						if(self.ALLSYNC.LIST.length == 1)
							self.ALLSYNC.load();
					}
					else{
						thiz.SYNC = false;
						thiz.add(src, callback, error);
						
						if(thiz.LIST.length == 1)
							thiz.load();
					}
				},
				
				/*
				 * @member Sarue.Require
				 * @method [public] add
				 * @param {string} src
				 * @param {function} success ?
				 * @param {function} error ?
				 * @description Adiciona o script na lista da instancia atual
				 */
				add: function(src, callback, error){
					var thiz = Sarue._bios ? Sarue.abstracts.Require.propertys : this,
						self = Sarue._bios ? Sarue.abstracts.Require.abstracts : this.self;

					self.addInList(src, callback, error, thiz.SYNC, thiz.LIST);
				},
				
				/*
				 * @member Sarue.Require
				 * @method [public] load
				 * @description Carrega a lista da instancia na ordem
				 */
				load: function(){
					var thiz = Sarue._bios ? Sarue.abstracts.Require.propertys : this,
						self = Sarue._bios ? Sarue.abstracts.Require.abstracts : this.self;
						
					self.load(thiz);
				}
			},
			
			abstracts:{
				INSTANCES: [],
				COMPLETED: {},
				ALLSYNC: null,
				
				/*
				 * @member Sarue.Require
				 * @property [static]{object} options
				 * @description Opções estaticas
				 */
				options: {
					/*
					 * @member Sarue.Require:options
					 * @property [static]{function} finish
					 * @description Função chamada quando a pilha de todas instancias terminar
					 */
					finish: function(){},
					/*
					 * @member Sarue.Require:options
					 * @property [static]{function} complete
					 * @description Função chamada quando cada script é carregado
					 */
					complete: function(){}
				},
				
				/*
				 * @member Sarue.Require
				 * @method [static] addInList
				 * @param {string} src
				 * @param {function} success ?
				 * @param {function} error ?
				 * @param {boolean} async ?
				 * @param {array} list
				 * @description Adiciona um script na lista de uma instancia
				 */
				addInList: function(src, callback, error, sync, list){
					var self = Sarue._bios ? Sarue.abstracts.Require.abstracts : this;
					
					if (!self.COMPLETED[src]) {
						list.push({
							src: self.getSrc(src),
							callback: callback || function(){},
							error: error || function(){},
							sync: sync
						});
					}
				},
				
				/*
				 * @member Sarue.Require
				 * @method [static] getSrc
				 * @param {string} src
				 * @return {string} src
				 * @description Pega src do arquivo junto com sua url
				 */
				getSrc: function(src){
					var path = Sarue.PATHJS || Sarue.abstracts.PATHJS;
					return (src.search('http') != -1) ? src : path + src;
				},
				
				/*
				 * @member Sarue.Require
				 * @method [static] load
				 * @param {instance} instance
				 * @description Carrega a lista de uma instancia na ordem
				 */
				load: function(instance){
					var self = Sarue._bios ? Sarue.abstracts.Require.abstracts : this, 
						loader = Sarue._bios ? Sarue.abstracts.Loader.abstracts : Sarue.Loader,
						obj = instance.LIST[0];
					
					if (obj) {
						obj.element = loader.js(obj.src, function(){
							self.COMPLETED[obj.src] = true;
							
							//call complete event
							instance.options.complete.call(instance.options.complete, obj.element, true);
							
							//call calback event
							obj.callback.apply(this, arguments);
							
							self.options.complete.call(self.options.complete, obj.element, true);
							
							//remove item of list
							instance.LIST.shift();
							self.load(instance);
						}, function(){
							self.COMPLETED[obj.src] = false;
							
							//call complete event
							instance.options.complete.call(instance.options.complete, obj.element, false);
							
							//call calback event
							obj.error.apply(this, arguments);
							
							self.options.complete.call(self.options.complete, obj.element, false);
							
							//remove item of list
							instance.LIST.shift();
							self.load(instance);
							
							return null;
						}, obj.sync);
					}
					else {
						if (instance.options.finish) {
							instance.options.finish.call(instance.options.finish, arguments);
							instance.options.finish = null;
						}
						if (self.options.finish) {
							var allInstanceFinish = true;
							
							for(var x = 0; x < self.INSTANCES.length; x++)
								allInstanceFinish = allInstanceFinish && (self.INSTANCES[x].LIST.length == 0);
							
							if (allInstanceFinish) {
								self.options.finish.call(self.options.finish, arguments);
								self.options.finish = null;
							}
						}
					}
					
				}
			}
		}
	},
	/*
	 * @member Sarue
	 * @property [private]{object} _bios
	 * @description Objeto de funções que inicializa o Sarue
	 */
	_bios: {
		/*
		 * @member Sarue._bios
		 * @property [private]{function} run
		 * @description Roda as funções necessárias da inicialização do bios
		 */
		run: function(){
			this.setup();			
			this.convert();
		},
		/*
		 * @member Sarue._bios
		 * @property [private]{function} setup
		 * @description Seta os valores das variaveis necessárias do Sarue
		 */
		setup: function(){
			Sarue.abstracts.REFERENCE = document.getElementById('sarue_control') || document.getElementByTagName('script')[0];
			
			var PATH = /\:\/\//.test(Sarue.abstracts.REFERENCE.src) ? Sarue.abstracts.REFERENCE.src : Sarue.abstracts.Tools.abstracts.getPath(window.location.href) + Sarue.abstracts.REFERENCE.src;
			
			Sarue.abstracts.PATHFW = Sarue.abstracts.Tools.abstracts.getPath(PATH);
			Sarue.abstracts.PATHJS = Sarue.abstracts.Tools.abstracts.getPath(Sarue.abstracts.PATHFW);
			Sarue.abstracts.ADDONS = Sarue.abstracts.Tools.abstracts.getHash(PATH).split(';');
			
			delete Sarue._bios.setup;
		},
		/*
		 * @member Sarue._bios
		 * @property [private]{function} klass
		 * @description Converte o objeto Sarue em uma Classe
		 */
		klass: function(obj){
			var propertys, abstracts;
			
			if(!obj) return obj;
			
			if (obj['propertys']) {
				propertys = {};
				for (var key in obj.propertys) 
					propertys[key] = this.klass(obj.propertys[key]);
			}
			
			if (obj['abstracts']) {
				abstracts = {};
				for (var key in obj.abstracts) 
					abstracts[key] = this.klass(obj.abstracts[key]);
			}
					
			return (propertys || abstracts) ? new Class(propertys, abstracts) : obj;
				
		},
		/*
		 * @member Sarue._bios
		 * @property [private]{function} convert
		 * @description Requisita o arquivo necessário para converter o Sarue em Classe
		 */
		convert: function(){
			//Convert Sarue Object in sarue class
			var Klass;
			
			Sarue.abstracts.Require.propertys.construct(Sarue.abstracts.PATHFW + 'class.js', function(){
				Sarue.abstracts.Require.propertys.LIST = [];
				Sarue.abstracts.Require.abstracts.ALLSYNC = null;
				
				Sarue.abstracts.Class = Class;
				
				Klass = Sarue._bios.klass(Sarue);
				delete Sarue;
				
				window['Sarue'] = Klass;
				window['Sarue'].initialize();
			});
			
			delete Sarue._bios.convert;
		}
	}
};

Sarue._bios.run();